package com.wondertek.service.service.sys.impl;

import cn.hutool.captcha.generator.RandomGenerator;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.BCrypt;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.wondertek.common.constant.Mobile;
import com.wondertek.common.enums.StatusEnum;
import com.wondertek.common.exception.AppException;
import com.wondertek.common.util.base.IpUtils;
import com.wondertek.common.util.base.SmsUtil;
import com.wondertek.common.util.security.LoginUser;
import com.wondertek.common.util.security.SecurityUtil;
import com.wondertek.service.dto.app.req.*;
import com.wondertek.service.dto.sys.res.SysLoginRes;
import com.wondertek.service.dto.sys.res.SysUserRes;
import com.wondertek.service.entity.business.ConsumerPo;
import com.wondertek.service.entity.sys.SysRolePo;
import com.wondertek.service.entity.sys.SysUserPo;
import com.wondertek.service.mapper.sys.SysConsumerMapper;
import com.wondertek.service.service.sys.*;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @description:
 * @author: qinjianhui
 * @create: 2024-07-17 09:53
 **/
@Slf4j
@Service
public class SysConsumerServiceImpl extends ServiceImpl<SysConsumerMapper, ConsumerPo> implements ISysConsumerService {
    @Resource
    private ISysUserService userService;

    @Resource
    private ISysMenuService sysMenuService;

    @Resource
    private ISysRoleService roleService;

    @Resource
    private RedisTemplate<String,Object> redisTemplate;

    @Resource
    private ISysEmailConfigService emailConfigService;

    @Resource
    private ISysFileService sysFileService;

    private static final String LOGIN_USER_KEY = "iot:dh:app:login:user:";
    private static final long LOGIN_EXPIRE =  60*60*24*7L;
    private static final long MOBILE_CAPTCHA_EXPIRE = 60L;
    private static final String LOGIN_MOBILE_KEY = "iot:dh:app:mobile:smsCode:";
    private static final String LOGIN_TOKEN_KEY = "iot:dh:app:login:token:";
    private static final long LOGIN_CAPTCHA_EXPIRE = 60L*3;

    /*
     * 获取用户信息
     * */
    @Override
    public ConsumerPo getUser(){
        String id = SecurityUtil.getLoginUser().getUserId();
        ConsumerPo po = this.getById(id);
        return po;
    }

    @Override
    public void logout(String token) {
        LoginUser loginUser = loadLoginUser(token);
        cleanToken(loginUser.getUserId());
    }

    /*
     * 短信获取验证码
     * */
    @Override
    public void massageCode(String mobile) throws Exception {
        this.checkMobileSmsStatus(mobile);
        RandomGenerator randomGenerator = new RandomGenerator("0123456789", 4);
        String smsCode = randomGenerator.generate();
        SmsUtil.sendMessage(mobile,String.format("您好，您的登录验证码是%s，有效期3分钟。",smsCode));
        redisTemplate.opsForValue().set(mobile+smsCode,smsCode,
                LOGIN_CAPTCHA_EXPIRE, TimeUnit.SECONDS);
        log.info("短信验证码发送成功 mobile {} smsCode {}",mobile,smsCode);
    }

    /*
    * 修改个人信息
    * */
    @Override
    public void updateInfo(AppAuthUpdateInfoReq req) {
        String id = SecurityUtil.getLoginUser().getUserId();
        ConsumerPo po = this.getById(id);
        po.setNickname(req.getNickname());
        po.setMobile(req.getMobile());
        po.setEmail(req.getEmail());
        po.setAvatar(req.getAvatar());
        this.updateById(po);
    }

    /*
     * 重置密码
     * */
    @Override
    public void restPassword(AppAuthUpdatePasswordReq passwordReq){
        ConsumerPo consumerPo = lambdaQuery().eq(ConsumerPo::getEmail,passwordReq.getEmail()).one();
        if(consumerPo==null) throw new AppException("email.not.register");
        consumerPo.setPassword(BCrypt.hashpw(passwordReq.getPassword()));
        this.updateById(consumerPo);
    }

    /*
    * 注册
    * */
    @Override
    public void register(AppAuthRegisterReq registerReq) {
        //先验证邮箱是否已经注册
        LambdaQueryWrapper<ConsumerPo> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(ConsumerPo::getEmail,registerReq.getEmail());
        if (this.count(wrapper)>0){
            throw new AppException("email.register");
        }
        ConsumerPo consumerPo = new ConsumerPo();
        consumerPo.setNickname(registerReq.getNickname());
        consumerPo.setEmail(registerReq.getEmail());
        consumerPo.setPassword(BCrypt.hashpw(registerReq.getPassword()));
        consumerPo.setStatus(StatusEnum.ENABLE.getCode());
        consumerPo.setMobile(registerReq.getMobile());
        this.save(consumerPo);
    }

    /*
     * 手机注册
     * */
    @Override
    public void mobileRegister(AppAuthMobileRegisterReq registerReq){
        //先验证邮箱是否已经注册
        LambdaQueryWrapper<ConsumerPo> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(ConsumerPo::getMobile,registerReq.getMobile());
        if (this.count(wrapper)>0){
            throw new AppException("phone.register");
        }
        ConsumerPo consumerPo = new ConsumerPo();
        consumerPo.setMobile(registerReq.getMobile());
        consumerPo.setPassword(BCrypt.hashpw(registerReq.getPassword()));
        consumerPo.setStatus(StatusEnum.ENABLE.getCode());
        consumerPo.setCountryCode(registerReq.getCountryCode());
        this.save(consumerPo);
    }

    /*
    * 登陆
    * */
    @Override
    public SysLoginRes login(AppAuthLoginReq loginReq) {
        //1-操作员登陆
        if(loginReq.getType()==1){
            SysUserPo sysUserPo = userService.lambdaQuery()
                    .eq(SysUserPo::getEmail, loginReq.getEmail())
                    .one();
            if(sysUserPo == null){
                throw new AppException("email.not.register");
            }
            if (!BCrypt.checkpw(loginReq.getPassword(), sysUserPo.getPassword())) {
                throw new AppException("user.password.error");
            }
            if(StatusEnum.DISABLE.getCode().equals(sysUserPo.getStatus())){
                throw new AppException("user.disabled");
            }
            List<SysRolePo> roleList = roleService.findListByUserId(sysUserPo.getId());
            Set<String> roleCodes = roleList.stream().map(SysRolePo::getRoleCode)
                    .filter(Objects::nonNull)
                    .collect(Collectors.toSet());
            // 角色判断
            if (CollUtil.isEmpty(roleCodes) || !roleCodes.contains("installer")) {
                throw new AppException("authority.error");
            }
            LoginUser loginUser = loginUser(sysUserPo.getId());
            return loginInfo(loginUser.getToken());
        }else{
            //2-用户登录
            LambdaQueryWrapper<ConsumerPo> wrapper = new LambdaQueryWrapper<>();
            wrapper.or(i -> i.eq(ConsumerPo::getMobile,loginReq.getEmail()));
            wrapper.or(i -> i.eq(ConsumerPo::getEmail,loginReq.getEmail()));
            ConsumerPo consumerPo = this.getOne(wrapper);
            if(consumerPo == null){
                throw new AppException("email.not.register");
            }
            if(!BCrypt.checkpw(loginReq.getPassword(),consumerPo.getPassword())){
                throw new AppException("user.password.error");
            }
            if(StatusEnum.DISABLE.getCode().equals(consumerPo.getStatus())){
                throw new AppException("user.disabled");
            }
            //尝试清除之前的登录
            cleanToken(consumerPo.getId());
            //创建token
            String token = IdUtil.fastSimpleUUID();
            LoginUser loginUser = new LoginUser();
            loginUser.setUserId(consumerPo.getId());
            loginUser.setUsername(loginReq.getEmail());
            loginUser.setNickname(consumerPo.getNickname());
            loginUser.setEmail(consumerPo.getEmail());
            loginUser.setMobile(consumerPo.getMobile());
            loginUser.setType("2");
            loginUser.setToken(token);
            //保存用户关联token
            redisTemplate.opsForValue().set(LOGIN_USER_KEY + consumerPo.getId(),token,
                    LOGIN_EXPIRE, TimeUnit.SECONDS);
            //保存token关联用户
            redisTemplate.opsForValue().set(LOGIN_TOKEN_KEY + token,loginUser,
                    LOGIN_EXPIRE,TimeUnit.SECONDS);
            SecurityUtil.SecurityContext.setLoginUser(loginUser);
            SysLoginRes sysLoginRes = new SysLoginRes();
            BeanUtil.copyProperties(loginUser,sysLoginRes);
            sysLoginRes.setId(consumerPo.getId());
            sysLoginRes.setAvatar(consumerPo.getAvatar());
            sysLoginRes.setAccessToken(loginUser.getToken());
            return sysLoginRes;
        }
    }

    /*
    * 邮箱发送验证码
    * */
    @Override
    public String getMailCode(String email) {
//        LambdaQueryWrapper<ConsumerPo> wrapper = new LambdaQueryWrapper<>();
//        wrapper.eq(ConsumerPo::getEmail,email);
//        wrapper.eq(ConsumerPo::getStatus,StatusEnum.ENABLE.getCode());
//        long count = this.count(wrapper);
//        if(count==0){
//            throw new AppException("emial.not.register");
//        }
        RandomGenerator randomGenerator = new RandomGenerator("0123456789", 4);
        String generate = randomGenerator.generate();
        log.info("----- 邮箱："+email+",验证码："+generate);
        //邮箱发送
//        gmailService.sendSimpleMessage(email,"verification code",generate);
        emailConfigService.send(Lists.newArrayList(email), generate);
        //保存验证码到redis
        redisTemplate.opsForValue().set(email+generate,generate,
                LOGIN_CAPTCHA_EXPIRE, TimeUnit.SECONDS);
        return generate;
    }

    /*
     * 短信验证码校验
     * */
    @Override
    public boolean checkMassageCode(AppAuthCheckMassageCodeReq req){
        String massageCode = (String) redisTemplate.opsForValue().get(req.getMobile()+req.getCode());
        if(req.getCode().equals(massageCode)){
            return true;
        }else {
            return false;
        }
    }

    @Override
    public SysUserRes getInstallerUserInfo() {
        String id = SecurityUtil.getLoginUser().getUserId();
        SysUserPo sysUserPo = userService.getById(id);
        sysUserPo.setPassword(null);
        SysUserRes sysUserRes = new SysUserRes();
        if(ObjectUtil.isEmpty(sysUserPo)){
            return sysUserRes;
        }
        BeanUtil.copyProperties(sysUserPo,sysUserRes);
        sysUserRes.setAvatarUrl(sysFileService.getViewFile(sysUserPo.getAvatar()));
        return sysUserRes;
    }

    @Override
    public void installerRestPassword(AppAuthInstallerRestPasswordReq passwordReq) {
        if(!StrUtil.equals(passwordReq.getNewPassword(), passwordReq.getNewConfirmPassword())){
            throw new AppException("password.not.match");
        }
        String id = SecurityUtil.getLoginUser().getUserId();
        SysUserPo sysUserPo = userService.getById(id);
        sysUserPo.setPassword(BCrypt.hashpw(passwordReq.getNewPassword()));
        userService.updateById(sysUserPo);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateFcmToken(String token) {
        this.lambdaUpdate().set(ConsumerPo::getFcmToken,null).eq(ConsumerPo::getFcmToken,token).update();
        String userId = SecurityUtil.getLoginUser().getUserId();
        this.lambdaUpdate().set(ConsumerPo::getFcmToken,token).eq(ConsumerPo::getId,userId).update();
    }

    /*
    * 校验邮箱验证码
    * */
    @Override
    public boolean checkCode(AppAuthCheckCodeReq req){
        String emailCode = (String) redisTemplate.opsForValue().get(req.getEmail()+req.getCode());
        if(req.getCode().equals(emailCode)){
            return true;
        }else {
            return false;
        }
    }

    private LoginUser loginUser(String userId){
        SysUserPo userPo = userService.getById(userId);
        //尝试清除之前的登录
        cleanToken(userId);
        LoginUser loginUser = new LoginUser();
        BeanUtils.copyProperties(userPo,loginUser);
        loginUser.setUserId(userId);
        loginUser.setUsername(userPo.getEmail());
        loginUser.setType("1");

        //创建token
        String token = IdUtil.fastSimpleUUID();
        loginUser.setToken(token);
        //保存用户关联token
        redisTemplate.opsForValue().set(LOGIN_USER_KEY + userId,token,
                LOGIN_EXPIRE, TimeUnit.SECONDS);
        //保存token关联用户
        redisTemplate.opsForValue().set(LOGIN_TOKEN_KEY + token,loginUser,
                LOGIN_EXPIRE,TimeUnit.SECONDS);
        SecurityUtil.SecurityContext.setLoginUser(loginUser);
        return loginUser;
    }

    public void cleanToken(String userId){
        String token = (String) redisTemplate.opsForValue().get(LOGIN_USER_KEY + userId);
        if(StrUtil.isNotBlank(token)){
            redisTemplate.delete(LOGIN_USER_KEY + userId);
            redisTemplate.delete(LOGIN_TOKEN_KEY + token);
        }
    }

    public SysLoginRes loginInfo(String token) {
        LoginUser loginUser = loadLoginUser(token);
        SysUserPo userPo = userService.getById(loginUser.getUserId());
        SysLoginRes sysLoginRes = new SysLoginRes();
        BeanUtil.copyProperties(userPo,sysLoginRes);
        sysLoginRes.setAccessToken(loginUser.getToken());
        sysLoginRes.setAvatarUrl(sysFileService.getViewFile(userPo.getAvatar()));
        return sysLoginRes;
    }

    public LoginUser loadLoginUser(String token) {
        return (LoginUser) redisTemplate.opsForValue().get(LOGIN_TOKEN_KEY + token);
    }

    public void checkMobileSmsStatus(String mobile) {
        this.checkMobileConcurrency(mobile, 2);
        if (redisTemplate.opsForValue().get(Mobile.MOBILE_STATUS_KEY + "-" + mobile) == null) {
            redisTemplate.opsForValue().set(Mobile.MOBILE_STATUS_KEY + "-" + mobile, mobile,MOBILE_CAPTCHA_EXPIRE, TimeUnit.SECONDS);
        } else {
            throw new AppException("captcha.repeat");
        }
    }

    private void checkMobileConcurrency(String mobile, int count) {
        String ip = IpUtils.getIpAddr();
        if (redisTemplate.opsForValue().get(LOGIN_MOBILE_KEY + "-" + ip) != null) {
            if (Integer.parseInt(Objects.requireNonNull(redisTemplate.opsForValue().get(LOGIN_MOBILE_KEY + "-" + ip)).toString()) >= count) {
                throw new AppException("ip.repeat");
            }
            redisTemplate.opsForValue().set(LOGIN_MOBILE_KEY + "-" + ip,
                    Integer.parseInt(Objects.requireNonNull(redisTemplate.opsForValue().get(LOGIN_MOBILE_KEY + "-" + ip)).toString()) + 1,
                    MOBILE_CAPTCHA_EXPIRE, TimeUnit.SECONDS);
        } else {
            redisTemplate.opsForValue().set(LOGIN_MOBILE_KEY + "-" + ip, 1,
                    MOBILE_CAPTCHA_EXPIRE, TimeUnit.SECONDS);
        }
        if (redisTemplate.opsForValue().get(LOGIN_MOBILE_KEY + "-" + mobile) != null) {
            if (Integer.parseInt(Objects.requireNonNull(redisTemplate.opsForValue().get(LOGIN_MOBILE_KEY + "-" + mobile)).toString()) >= count) {
                throw new AppException("ip.repeat");
            }
            redisTemplate.opsForValue().set(LOGIN_MOBILE_KEY + "-" + mobile,
                    Integer.parseInt(Objects.requireNonNull(redisTemplate.opsForValue().get(LOGIN_MOBILE_KEY + "-" + mobile)).toString()) + 1,
                    MOBILE_CAPTCHA_EXPIRE, TimeUnit.SECONDS);
        } else {
            redisTemplate.opsForValue().set(LOGIN_MOBILE_KEY + "-" + mobile, 1,
                    MOBILE_CAPTCHA_EXPIRE, TimeUnit.SECONDS);
        }
    }
}

